/**
 * 
 * AxLib
 * 
 * Author: WaveF
 * QQ: 298010937
 * 
 * UPDATE:
 * 
 * v3.4.0
 * - 重写jsonbox类
 * - 增加了一些plugins
 * 
 * v3.3.0
 * - 增加插件
 * 
 * v3.2.9
 * - 将 yepnope 替换为 axyepnope（可能并没有不同）
 * - 完善页面跳转方法的钩子注入（方法名有变更）
 * $axure.beforeNav((options,stop)=>{
 *   console.log(options);
 *   //stop();
 * });
 * $axure('@btn').$().click(e=>{
 *   $axure.navigate({ url: 'page_1.html', target: 'frame', includeVariables: true, frame: $iframe.get(0) });
 * });
 * 
 * v3.2.8
 * - 修正因为useYepnope导致的延迟问题
 * 
 **/

(function () {

  let _plugins;

  main();

  function main() {
    if (!window.$axure || !window.$) {
      trace('请先加载 $axure 和 jquery!');
      return;
    }

    useYepNope();
    Utils();
    InitAxlib();

    _plugins = {
      toast: {
        vars: '$.toast',
        files: [
          'https://ax.minicg.com/plugins/toast/jquery.toast.min.css',
          'https://ax.minicg.com/plugins/toast/jquery.toast.min.js'
        ]
      },
      toastify: {
        vars: 'Toastify',
        files: [
          'https://ax.minicg.com/plugins/toastify/toastify-all.min.js'
        ]
      },
      swal2: {
        vars: 'Swal',
        files: [
          'https://ax.minicg.com/plugins/swal2/bootstrap-4.min.css',
          'https://ax.minicg.com/plugins/swal2/sweetalert2.min.js'
        ]
      },
      keepfit: {
        vars: 'keepfit',
        files: [
          'https://ax.minicg.com/plugins/keepfit/keepfit.min.js'
        ]
      },
      aframe: {
        vars: 'aframe',
        files: [
          'https://ax.minicg.com/aframe-130.min.js',
        ]
      },
      preload: {
        vars: 'preload',
        files: [
          'https://ax.minicg.com/preloadjs.min.js',
        ]
      },
      pico: {
        vars: 'pico',
        files: [
          'https://ax.minicg.com/pico.min.css',
        ]
      },
      crypto: {
        vars: 'crypto',
        files: [
          'https://ax.minicg.com/crypto-js.min.js',
        ]
      },
      faker: {
        vars: 'faker',
        files: [
          'https://ax.minicg.com/faker.min.js',
        ]
      },
      tailwind: {
        vars: 'tailwind',
        files: [
          'https://ax.minicg.com/tailwind.min.css',
          'https://ax.minicg.com/tailwindcss-jit-cdn.umd.min.js',
        ]
      },
      echarts: {
        vars: 'echarts',
        files: [
          'https://ax.minicg.com/echarts.min.js',
          'https://ax.minicg.com/echarts-gl.min.js',
        ]
      },
      tweakpane: {
        vars: 'Tweakpane',
        files: [
          'https://ax.minicg.com/plugins/tweakpane/tweakpane.min.js',
          'https://ax.minicg.com/plugins/tweakpane/tweakpane-plugin-essentials.min.js',
          'https://ax.minicg.com/plugins/tweakpane/tweakpane-plugin-rotation.min.js',
          'https://ax.minicg.com/plugins/tweakpane/tweakpane-textarea-plugin.min.js',
          'https://ax.minicg.com/plugins/tweakpane/tweakpane-plugin-camerakit.min.js',
          'https://ax.minicg.com/plugins/tweakpane/tweakpane-plugin-image.min.js',
          'https://ax.minicg.com/plugins/tweakpane/tweakpane-plugin-infodump.min.js',
          'https://ax.minicg.com/plugins/tweakpane/tweakpane-plugin-profiler.min.js',
        ]
      },
    };

    class jsonbin {
      _id = '';
      _apiKey = '';
      _accessKey = '';
  
      constructor(id) {
          this._id = id;
      }
  
      get id() {
          return this._id;
      }
  
      set id(id) {
          this._id = id;
      }
  
      get apiKey() {
          return this._apiKey;
      }
  
      set apiKey(key) {
          this._apiKey = key;
      }
  
      get accessKey() {
          return this._accessKey;
      }
  
      set accessKey(key) {
          this._accessKey = key;
      }
  
      read() {
          const binId = this._id;
          const binVersion = 'latest';
  
          return fetch(`https://api.jsonbin.io/v3/b/${binId}/${binVersion}`, {
              headers: {
                  'Content-Type': 'application/json',
                  'X-Access-Key': this._accessKey,
              }
          })
          .then(response => response.json())
          .catch(error => { console.error(error); });
      }
  
      update(data) {
          const binId = this._id;
  
          return fetch(`https://api.jsonbin.io/v3/b/${binId}`, {
              method: 'PUT',
              headers: {
                  'Content-Type': 'application/json',
                  // 'X-Access-Key': this._accessKey,
              },
              body: JSON.stringify(data)
          })
          .then(response => response.json())
          .catch(error => { console.error(error); });
      }
  
      create(_name, _private=false) {
          // if (this._id) {
          //     return new Promise((resolve, reject) => {
          //         resolve({id:this._id});
          //     });
          // }
  
          const headers = {
              'Content-Type': 'application/json',
              'X-Bin-Private': _private,
          };
  
          if (this._apiKey != '') {
              headers['X-Master-Key'] = this._apiKey;
          }  
  
          if (this._accessKey != '' && this._apiKey == '') {
              headers['X-Access-Key'] = this._accessKey;
          }        
  
          if (_name) {
              headers['X-Bin-Name'] = _name;
          }
  
          return fetch(`https://api.jsonbin.io/v3/b`, {
              method: 'POST',
              headers,
              body: JSON.stringify({foo:'bar'})
          })
          .then(response => response.json())
          .catch(error => { console.error(error); });
      }
  
      delete(binId=this._id) {
          const headers = {};
  
          if (this._apiKey != '') {
              headers['X-Master-Key'] = this._apiKey;
          }  
  
          if (this._accessKey != '' && this._apiKey == '') {
              headers['X-Access-Key'] = this._accessKey;
          }
  
          return fetch(`https://api.jsonbin.io/v3/b/${binId}`, {
              method: 'DELETE',
              headers,
          })
          .then(response => response.json())
          .catch(error => { console.error(error); });
      }
    }

    // 附加全局方法
    window.jsonbin = jsonbin;
    window.AXLIB = window.axlib = {
      name: 'axlib', version: '3.4.0',

      use, // 加载其他模块
      layout, // 预设的界面元素显隐及缩放
      trace, // 显示一段预设样式的文本
      jsonbin, // 调用jsonbin v3的方法在线读写数据
      hosting, // 参数定义同步到哪里，默认是axhub
      loading, // 插入一个加载动画
      find, // 便捷提取指定元件名的对象，示例：let els = axlib.find('sym1', 'sym2'...); let { sym1, sym2 } = els;
      saveTextFile, // 传入字符串与文件名保存文件
      testExp, // 等待测试表达式的方法
      loadJsModule, // 以 es module 方式加载 js
      
      plugins: _plugins,
      formatRepeaterData: $axure.ex.formatRepeaterData,
      setGlobalVar: $axure.setGlobalVariable,
      getGlobalVar: $axure.getGlobalVariable,
      getOverlapData: $axure.getOverlapData,
      getDiffData: $axure.getDiffData,

    };
  }

  function InitAxlib() {
    $axure.ex = { fn: {} };

    // _showMsg = new Function( 'return ' + top.$axure.messageCenter.dispatchMessageRecursively.toString() );

    // 改写并拓展Axure的私有对象
    $axure.internal(function ($ax) {

      /**
       * 增强中继器相关方法
       * 注：仅支持单个操作，要确保每个元素都有不同ID，就可以用循环逐个修改
       */

      // 刷新数据（要定义在最前面，因为后面会引用到）
      $axure.ex.refreshRepeater = $ax.repeater.refreshRepeater;
      $ax.public.fn.refreshRepeater = function () {
        let elementId = this.getElementIds()[0];
        $axure.ex.refreshRepeater(elementId);
        return this;
      };
      $ax.repeater.refreshRepeater = function(elementId, etc) {
        $axure.ex.refreshRepeater(...arguments);
        $(`#${elementId}`).trigger('REPEATER_REFRESH');
      };

      // 好像是获取行号，从1开始索引，因为后面要用到所以保留
      $axure.ex.getRepeaterRows = $ax.repeater.getAllItemIds;
      $ax.public.fn.getRepeaterRows = function () {
        return $axure.ex.getRepeaterRows(this.getElementIds()[0]);
      };

      // 通过ID获取实例
      $axure.ex.getRepeater = function (elementId) {
        let repeater;
        $axure(function (obj) {
          return obj.type == 'repeater';
        }).each(function (obj, id) {
          if (id == elementId) {
            repeater = obj;
          }
        });
        return repeater;
      };

      // 格式化数据
      $axure.ex.formatRepeaterData = function(data, options) {
        // 根据 options 参数对数据进行格式化
        options = options || { format:'auto', key:undefined, first:false, trim:true };
        let newData;
        let format = options['format'].toLowerCase();
        if (format === 'default') {
          newData = data;
        } else if (format === 'auto') {
          let arr = [];
          for (let i = 0; i < data.length; i++) {
            let item = data[i];
            let obj = {};
            for (let k in item) {
              obj[k] = item[k].text;
            }
            arr.push(obj);
          }
          newData = arr;
        } else if (format === 'row') {
          let result = {};
          for (let i = 0; i < data.length; i++) {
            let item = data[i];
            let obj = {};
            let key = Object.values(item)[0].text;
            let val = [];
            for (let k in item) {
              let sub = item[k];
              if (sub.text != key) {
                val.push(sub.text);
              }
            }
            if (options.first) { val = val[0]; }
            obj[key] = val;
            $.extend(result, obj);
          }
          newData = result;
        } else if (format === 'column' || format === 'col') {
          let obj = {};
          for (let i = 0; i < data.length; i++) {
            let item = data[i];
            for (k in item) {
              if (!obj[k]) {
                obj[k] = [item[k].text];
              } else {
                obj[k].push(item[k].text);
              }
            }
          }
          newData = obj;
        } else if (format === 'key') {
          if (!options.key) {
            console.log('未提供Key名，将不会进行格式化...');
            newData = data;
          } else {
            let result = {};
            let pKey = options.key;
            for (let i = 0; i < data.length; i++) {
              let item = data[i];
              let key = item[pKey].text;
              let val = [];
              let obj = {};
              for (let k in item) {
                if (k != pKey) {
                  val.push(item[k].text);
                }
              }
              if (options.first) { val = val[0]; }
              obj[key] = val;
              $.extend(result, obj);
            }
            newData = result;
          }
        } else if (format === 'tree') {
          let arr = [];
          for (let i = 0; i < data.length; i++) {
            let item = data[i];
            let obj = {};
            for (let k in item) { obj[k] = item[k].text; }
            arr.push(obj);
          }
          newData = tree(arr);
          options.trim && trimTreeData(newData);
        } else {
          trace(`未注册的格式化参数: ${format}，请使用此定义 { format: 'auto' || 'row' || 'column' || 'tree' || 'key' } ，其中 key 模式需额外提供索引键列名称.`);
          newData = data;
        }
        return newData;
      }
      $axure.formatRepeaterData = $axure.ex.formatRepeaterData;

      // 获取数据
      $axure.ex.getRepeaterData = function (elementId) {
        let ids = $ax.repeater.getAllItemIds(elementId);
        let columns = $axure.ex.getRepeater(elementId).dataProps;
        rows = [];
        for (let i = 0, il = ids.length; i < il; i++) {
          let row = {};
          for (let j = 0, jl = columns.length; j < jl; j++) {
            let name = columns[j].toLowerCase();
            let id = ids[i];
            if ((typeof (id) == 'string') && (id.indexOf('-') != -1))
              id = $ax.repeater.getItemIdFromElementId(id);
            let value = $ax.repeater.getData({}, elementId, ids[i], name, 'data');
            if (typeof (value) == 'object') {
              value = $ax.deepCopy(value);
              if (value.type === undefined) {
                value.type = 'text';
              }
              row[name] = value;
            } else {
              row[name] = {
                type: 'text',
                text: value
              };
            }
          }
          rows.push(row);
        }
        return rows;
      };
      $ax.public.fn.getRepeaterData = function (options) {
        let elementId = this.getElementIds()[0];
        let data = $axure.ex.getRepeaterData(elementId);
        data = $axure.ex.formatRepeaterData(data, options);
        return data;
      };

      // 获取初始化数据
      $axure.ex.getRepeaterDataOnce = function (elementId) {
        try {
          var result;
          // $axure(`#${id}`).refreshRepeater();
          return window.$axure((function (elementId) {
              return "repeater" === elementId.type
          })).each((function (obj, id) {
            elementId === id && (result = obj)
          })), result.data;
        } catch (err) {
            return console.error("Error：Can not get repeater data by id " + err);
        }
      };
      $ax.public.fn.getRepeaterDataOnce = function (options) {
        let elementId = this.getElementIds()[0];
        let data = $axure.ex.getRepeaterDataOnce(elementId);
        data = $axure.ex.formatRepeaterData(data, options);
        return data;
      };
      $axure.ex.getRepeaterInitedData = $axure.ex.getRepeaterDataOnce;
      $ax.public.fn.getRepeaterInitedData = $ax.public.fn.getRepeaterDataOnce;

      // 获取初始化与交互追加后的差异数据
      $axure.ex.getRepeaterDataDiff = function (elementId) {
        let data = $axure.ex.getRepeaterData(elementId);
        let dataOnce = $axure.ex.getRepeaterDataOnce(elementId);
        data.splice(0, dataOnce.length);
        return data;
      };
      $ax.public.fn.getRepeaterDataDiff = function (options) {
        let elementId = this.getElementIds()[0];
        let diff = $axure.ex.getRepeaterDataDiff(elementId);
        return $axure.ex.formatRepeaterData(diff, options);
      };

      // 获取初始化与交互追加后的差异数据
      $axure.ex.getRepeaterDataSame = function (elementId) {
        let data = $axure.ex.getRepeaterData(elementId);
        let dataOnce = $axure.ex.getRepeaterDataOnce(elementId);
        data.splice(dataOnce.length, data.length);
        return data;
      };
      $ax.public.fn.getRepeaterDataSame = function (options) {
        let elementId = this.getElementIds()[0];
        let diff = $axure.ex.getRepeaterDataSame(elementId);
        return $axure.ex.formatRepeaterData(diff, options);
      };

      // 追加数据
      $axure.ex.repeaterAddItem = $ax.repeater.addItem;
      $axure.ex.addRepeaterData = function (elementId, rows) {
        let event = { targetElement: undefined, srcElement: undefined };
        let repeater = $axure.ex.getRepeater(elementId);
        let columns = repeater.dataProps;
        let itemDefaultValue = { type: 'text', text: '' };

        for (let i = 0, ilen = rows.length; i < ilen; i++) {
          let source = rows[i];
          let target = {};
          for (let j = 0, jlen = columns.length; j < jlen; j++) {
            let column = columns[j];
            let item = source[column];

            if (item === undefined) {
               item = itemDefaultValue;
            } else if (!item.hasOwnProperty('type') && !item.hasOwnProperty('text')) {
              item = { type: 'text', text: item };
            } else {
              item = $ax.deepCopy(item);
            }
            target[column] = item;
          }
          $ax.repeater.addItem(elementId, target, event);
        }
      };
      $ax.public.fn.addRepeaterData = function (rows) {
        let elementId = this.getElementIds()[0];
        $axure.ex.addRepeaterData(elementId, rows);
        $axure.ex.refreshRepeater(elementId);
        return this;
      };
      // 这里会触发原生交互的添加数据
      $ax.repeater.addItem = function() {
        let elementId = arguments[0];
        $axure.ex.repeaterAddItem(...arguments);
        $(`#${elementId}`).trigger('REPEATER_DATA_ADDED', {...arguments});
      };


      // 移除N行数据
      $axure.ex.deleteItems = $ax.repeater.deleteItems;
      $ax.public.fn.deleteRepeaterData = function (rowsCount) {
        rowsCount = rowsCount || 1;
        let data = this.getRepeaterData();
        let newData = data.slice(0, data.length - rowsCount);
        let elementId = this.getElementIds()[0];
        $axure.ex.clearRepeaterData(elementId);
        $axure.ex.addRepeaterData(elementId, newData);
        $axure.ex.refreshRepeater(elementId);
        return this;
      };
      // 这里会触发原生交互的删除数据
      $ax.repeater.deleteItems = function() {
        let elementId = arguments[0];
        $axure.ex.deleteItems(...arguments);
        $(`#${elementId}`).trigger('REPEATER_DATA_DELETED', {...arguments});
      }


      // PUSH数据（意义不明）
      $axure.ex.pushItems = $ax.repeater.pushItems;
      $ax.repeater.pushItems = function() {
        let elementId = arguments[0];
        $axure.ex.pushItems(...arguments);
        $(`#${elementId}`).trigger('REPEATER_DATA_PUSH', {...arguments});
      }
      
      
      // 更新数据
      $axure.ex.updateEditItems = $ax.repeater.updateEditItems;
      $ax.repeater.updateEditItems = function() {
        let elementId = arguments[0];
        $axure.ex.updateEditItems(...arguments);
        $(`#${elementId}`).trigger('REPEATER_DATA_UPDATED', {...arguments});
      }


      // 获取重叠部分数据
      $ax.public.fn.getOverlapData = function (extraRawData, format) {
        let elementId = this.getElementIds()[0];
        let rep = $axure(`#${elementId}`);
        let latestRawData = rep.getRepeaterData({format:'default'});
        let overlapData = getOverlapData(extraRawData, latestRawData);
        return $axure.ex.formatRepeaterData(overlapData, format);;
      };

      

      // 设置数据
      $ax.public.fn.setRepeaterData = function (rows) {
        let elementId = this.getElementIds()[0];
        $axure.ex.clearRepeaterData(elementId);
        $axure.ex.addRepeaterData(elementId, rows);
        $axure.ex.refreshRepeater(elementId);
        $(`#${elementId}`).trigger('REPEATER_DATA_SET', {...arguments});
        return this;
      };
      
      // 清空数据
      $axure.ex.clearRepeaterData = function (elementId) {
        let ids = $axure.ex.getRepeaterRows(elementId);
        $ax.repeater.addEditItems(elementId, ids);
        $ax.repeater.deleteItems(elementId, {}, 'marked', undefined);
      };
      $ax.public.fn.clearRepeaterData = function () {
        let elementId = this.getElementIds()[0];
        $axure.ex.clearRepeaterData(elementId);
        $axure.ex.refreshRepeater(elementId);
        return this;
      };


      /**
       * 增强动态面板相关方法
       * 注：仅支持单个操作，要确保每个元素都有不同ID，就可以用循环逐个修改
       */

      // 增强动态面板相关方法
      $axure.ex.getPanelStates = function (elementId) {
        let result = [];
        for (let index = 1;; index++) {
          let element = $('#' + elementId + '_state' + (index - 1));
          if (!element.length) break;
          let name = element.attr('data-label');
          result[index] = name;
          result[name] = index;
        }
        return result;
      };
      $ax.public.fn.getPanelStates = function () {
        let states = $axure.ex.getPanelStates(this.getElementIds()[0]);
        return states;
      };


      $axure.ex.getCurrentPanelState = function (elementId) {
        let current;
        let states = $(`#${elementId}`).children(`.panel_state`);
        $.each(states, (idx, item) => {
          if ($(item).css('visibility')!='hidden' && $(item).css('display')!='none') {
            current = item;
          }
        });
        return current;
      };
      $ax.public.fn.getCurrentPanelState = function () {
        let current = $axure.ex.getCurrentPanelState(this.getElementIds()[0]);
        return current;
      };


      $axure.ex.setPanelStateByName = function (elementId, stateName, options, showWhenSet) {
        let states = $axure.ex.getPanelStates(elementId);
        for (let k = 0; k < states.length; k++) {
          if (states[k] == stateName) {
            let stateNum = k;
            $axure('#' + elementId).SetPanelState(stateNum, options || {}, showWhenSet || false);
          }
        }
      };
      $ax.public.fn.setPanelStateByName = function (stateName, options, showWhenSet) {
        let elementId = this.getElementIds()[0];
        $axure.ex.setPanelStateByName(elementId, stateName, options, showWhenSet);
        return this;
      };
      $ax.public.fn.setPanelStateByIndex = function (index, options, showWhenSet) {
        let elementId = this.getElementIds()[0];
        $axure('#' + elementId).SetPanelState(index, options || {}, showWhenSet || false);
        return this;
      };


      /**
       * 简化获取ID与节点的方法名
       */
      $ax.public.fn.getId = function () {
        return this.getElementIds()[0];
      };
      $ax.public.fn.getEl = function () {
        return this.getElements()[0];
      };
      $axure.find = find;


      /**
       * 修正axure无法获取全局变量的bug
       */
      $ax.public.getGlobalVariable = $ax.getGlobalVariable = function (name) {
        return $ax.globalVariableProvider.getVariableValue(name);
      };


      /**
       * 添加一些常用样式控制
       */
      $ax.public.fn.buttonMode = function (flag) {
        this.$().css('cursor', flag ? 'pointer' : 'default');
      };
      $ax.public.fn.ghostMode = function (flag) {
        this.$().css('pointer-events', flag ? 'none' : 'auto');
      };

      
      /**
       * 跳转页面或链接
       */
      
      // 设置页面跳转前的Hook
      // 注：页面载入时没有hook，如果要做载入前的转场动画需要自行处理，因为axlib开始执行的时间始终慢于页面渲染时
      $axure.ex.redirect = true;
      $axure.ex.beforeNavFn = function () {};
      $axure.beforeNav = function (callback) {
        $axure.ex.beforeNavFn = callback;
      };

      // 先缓存原本的跳转方法
      $axure.ex.navigate = $ax.public.navigate;

      // 改写内置的跳转函数
      $ax.public.navigate = $ax.navigate = function(options) {
        let stop = flag => { $axure.ex.redirect = false; };
        $axure.ex.beforeNavFn(options,stop);
        
        if (!$axure.ex.redirect) return;
        $axure.ex.navigate(options);
      };

      $axure.getOverlapData = getOverlapData;
      $axure.getDiffData = getDiffData;

    });

  }

  function Utils() {
    /**
     * 拓展原生JS
     */

    // 增加一个将字符串去除换行符的方法
    Object.defineProperty(String.prototype, 'minify', {
      enumerable: false,
      configurable: true,
      value: function () {
        return this.replace(/ *[\r|\n] */gm, '')
      }
    });


    /**
     * 拓展 JQuery
     */

    // 增加一个判断变量是否符合 JSON 格式的方法
    $.isJSON = function (str) {
      try {
        if (typeof JSON.parse(str) == "object") {
          return true;
        }
      } catch (e) {}
      return false;
    };

    // 增加一个判断 DOM 元素是否存在 Attribute 属性的方法
    $.fn.extend({
      'hasAttr': function (name) {
        let _attr = $(this).attr(name);
        if (typeof _attr !== typeof undefined && _attr !== false) {
          return true;
        } else {
          return false;
        }
      }
    });
  }

  function layout(mode) {
    if (self == top) return;
    mode = mode !== undefined ? mode : 0;

    let _mode = {
      hotspots: false,
      notes: false,
      scale: [0, 1, 2][1],
      console: false,
      sitemap: false,
      minimal: false,
      website: true
    };

    setTimeout(() => {
      let shell = window.parent.document;
      let hash = shell.location.hash;

      if (mode > 0) {
        if (hash.indexOf('&fn=0') < 0) {
          $(shell).find('#showNotesOption').click();
        }
        if (hash.indexOf('&hi=1') > -1) {
          $(shell).find('#showHotspotsOption').click();
        }
      }

      if (mode === 0) {
        $(shell).find('#maximizePanelContainer').show();
        $(shell).find(`.vpScaleOption[val="0"]`).click();
      } else if (mode === 1) {
        $(shell).find(`.vpScaleOption[val="0"]`).click();
        $(shell).find('#interfaceControlFrameMinimizeContainer').click();
        $(shell).find('#maximizePanelContainer').hide();
      } else if (mode === 2) {
        $(shell).find(`.vpScaleOption[val="1"]`).click();
        $(shell).find('#interfaceControlFrameMinimizeContainer').click();
        $(shell).find('#maximizePanelContainer').hide();
      } else if (mode === 3) {
        $(shell).find(`.vpScaleOption[val="2"]`).click();
        $(shell).find('#interfaceControlFrameMinimizeContainer').click();
        $(shell).find('#maximizePanelContainer').hide();
      }
    }, 0);
  }

  function loading(container, show, options) {
    options = options || { color:'#fff', opacity:0.2, scale:1.2, delay:0, dur:1, blending:'difference' };
    if (show === undefined) { show = true; }
    let display = show ? 'block' : 'none';

    let svg = `
      <svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 38 38" stroke="${options.color}" style="transform:scale(${options.scale});">
        <g fill="none" fill-rule="evenodd">
          <g transform="translate(1 1)" stroke-width="2">
            <circle stroke-opacity="${options.opacity}" cx="18" cy="18" r="18"/>
            <path d="M36 18c0-9.94-8.06-18-18-18">
              <animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="${options.dur}s" repeatCount="indefinite"/>
            </path>
          </g>
        </g>
      </svg>
    `;
    svg = svg.minify();

    if (!window.$axure.loadingCSSRulesInited) {
      $('head').append(`
        <style>
          html, body { height: 100%; }
          .ax-loading { display:${display}; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: transparent; z-index: 99999; mix-blend-mode:${options.blending}; pointer-events:none; transform: scale(.5); }
          .ax-loading-svg { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
        </style>
      `);
      window.$axure.loadingCSSRulesInited = true;
    }

    if ($(container).children('.ax-loading').length === 0) {
      $(container).append(`<div class="ax-loading"><div class="ax-loading-svg">${svg}</div></div>`);
    }
    return $(container).children('.ax-loading').get(0);
  }

  function trace() {
    console.group('%cAXLIB', 'color:#fff; font-weight:bold; background:#08f; padding:4px 8px; border-radius:3px;');
    console.log(...arguments);
    console.groupEnd();
  }

  function hosting(cloud) {
    cloud = cloud || 'axhub';
    if (cloud == 'axhub') {
      let btnUpload = $('#axhub-rp9-root a i', top.document);
      if (btnUpload.length === 0) {
        trace('无法同步, 你需要先为浏览器安装axhub插件!');
      } else {
        btnUpload.click();
      }
    }
  }

  function find () {
    let args = [...arguments];
    let obj = {};
    for (let i = 0; i < args.length; i++) {
      let name = args[i];
      let el = $axure('@' + name);
      if (el.$().length > 0) {
        obj[name] = el;
      }
    }
    return obj;
  }

  function use(args, cb) {
    console.log('%caxlib.use()方法已不再建议使用,请通过axlib.plugins获取插件列表,并自行用yepnope加载','color:#f30;font-weight:bold;');
    args = typeof args === 'string' ? [args] : args;
    if (!args.forEach) { console.log('Use array or string as argument!'); return; }
    args.forEach(mod => {
      let plugin = _plugins[mod];
      if (!plugin) { console.log(`[${mod}] - plugin not found!`); return; }
      yepnope({
        test: getNestedValue(window, plugin['vars']),
        nope: plugin.files,
        complete: () => {
          let res = getNestedValue(window, plugin['vars']);
          if (!axlib.plugins) { axlib.plugins = {}; }
          axlib.plugins[mod] = res;
          cb(axlib.plugins);
        }
      });
    });
  }

  function useYepNope() {
    !function(window,doc,undef){var docElement=doc.documentElement,sTimeout=window.setTimeout,firstScript=doc.getElementsByTagName("script")[0],toString={}.toString,execStack=[],started=0,noop=function(){},isGecko="MozAppearance"in docElement.style,isGeckoLTE18=isGecko&&!!doc.createRange().compareNode,insBeforeObj=isGeckoLTE18?docElement:firstScript.parentNode,isOpera=window.opera&&"[object Opera]"==toString.call(window.opera),isIE=!!doc.attachEvent&&!isOpera,strJsElem=isGecko?"object":isIE?"script":"img",strCssElem=isIE?"script":strJsElem,isArray=Array.isArray||function(obj){return"[object Array]"==toString.call(obj)},isObject=function(obj){return Object(obj)===obj},isString=function(s){return"string"==typeof s},isFunction=function(fn){return"[object Function]"==toString.call(fn)},globalFilters=[],scriptCache={},prefixes={timeout:function(resourceObj,prefix_parts){return prefix_parts.length&&(resourceObj.timeout=prefix_parts[0]),resourceObj}},dedup,handler,yepnope;function isFileReady(readyState){return!readyState||"loaded"==readyState||"complete"==readyState||"uninitialized"==readyState}function injectJs(src,cb,attrs,timeout,err,internal){var script=doc.createElement("script"),done,i;for(i in timeout=timeout||yepnope.errorTimeout,script.src=src,attrs)script.setAttribute(i,attrs[i]);cb=internal?executeStack:cb||noop,script.onreadystatechange=script.onload=function(){!done&&isFileReady(script.readyState)&&(done=1,cb(),script.onload=script.onreadystatechange=null)},sTimeout((function(){done||(done=1,cb(1))}),timeout),err?script.onload():firstScript.parentNode.insertBefore(script,firstScript)}function injectCss(href,cb,attrs,timeout,err,internal){var link=doc.createElement("link"),done,i;for(i in timeout=timeout||yepnope.errorTimeout,cb=internal?executeStack:cb||noop,link.href=href,link.rel="stylesheet",link.type="text/css",attrs)link.setAttribute(i,attrs[i]);err||(firstScript.parentNode.insertBefore(link,firstScript),sTimeout(cb,0))}function executeStack(){var i=execStack.shift();started=1,i?i.t?sTimeout((function(){("c"==i.t?yepnope.injectCss:yepnope.injectJs)(i.s,0,i.a,i.x,i.e,1)}),0):(i(),executeStack()):started=0}function preloadFile(elem,url,type,splicePoint,dontExec,attrObj,timeout){timeout=timeout||yepnope.errorTimeout;var preloadElem=doc.createElement(elem),done=0,firstFlag=0,stackObject={t:type,s:url,e:dontExec,a:attrObj,x:timeout};function onload(first){if(!done&&isFileReady(preloadElem.readyState)&&(stackObject.r=done=1,!started&&executeStack(),preloadElem.onload=preloadElem.onreadystatechange=null,first))for(var i in"img"!=elem&&sTimeout((function(){insBeforeObj.removeChild(preloadElem)}),50),scriptCache[url])scriptCache[url].hasOwnProperty(i)&&scriptCache[url][i].onload()}1===scriptCache[url]&&(firstFlag=1,scriptCache[url]=[]),"object"==elem?preloadElem.data=url:(preloadElem.src=url,preloadElem.type=elem),preloadElem.width=preloadElem.height="0",preloadElem.onerror=preloadElem.onload=preloadElem.onreadystatechange=function(){onload.call(this,firstFlag)},execStack.splice(splicePoint,0,stackObject),"img"!=elem&&(firstFlag||2===scriptCache[url]?(insBeforeObj.insertBefore(preloadElem,isGeckoLTE18?null:firstScript),sTimeout(onload,timeout)):scriptCache[url].push(preloadElem))}function load(resource,type,dontExec,attrObj,timeout){return started=0,type=type||"j",isString(resource)?preloadFile("c"==type?strCssElem:strJsElem,resource,type,this.i++,dontExec,attrObj,timeout):(execStack.splice(this.i++,0,resource),1==execStack.length&&executeStack()),this}function getYepnope(){var y=yepnope;return y.loader={load:load,i:0},y}(yepnope=function(needs){var i,need,chain=this.yepnope.loader;function satisfyPrefixes(url){var parts=url.split("!"),gLen=globalFilters.length,origUrl=parts.pop(),pLen=parts.length,res={url:origUrl,origUrl:origUrl,prefixes:parts},mFunc,j,prefix_parts;for(j=0;j<pLen;j++)prefix_parts=parts[j].split("="),(mFunc=prefixes[prefix_parts.shift()])&&(res=mFunc(res,prefix_parts));for(j=0;j<gLen;j++)res=globalFilters[j](res);return res}function getExtension(url){return url.split(".").pop().split("?").shift()}function loadScriptOrStyle(input,callback,chain,index,testResult){var resource=satisfyPrefixes(input),autoCallback=resource.autoCallback,extension=getExtension(resource.url);if(!resource.bypass){if(callback&&(callback=isFunction(callback)?callback:callback[input]||callback[index]||callback[input.split("/").pop().split("?")[0]]),resource.instead)return resource.instead(input,callback,chain,index,testResult);scriptCache[resource.url]?resource.noexec=dedup:scriptCache[resource.url]=1,chain.load(resource.url,resource.forceCSS||!resource.forceJS&&"css"==getExtension(resource.url)?"c":undef,resource.noexec,resource.attrs,resource.timeout),(isFunction(callback)||isFunction(autoCallback))&&chain.load((function(){getYepnope(),callback&&callback(resource.origUrl,testResult,index),autoCallback&&autoCallback(resource.origUrl,testResult,index),scriptCache[resource.url]=2}))}}function loadFromTestObject(testObject,chain){var testResult=!!testObject.test,group=testResult?testObject.yep:testObject.nope,always=testObject.load||testObject.both,callback=testObject.callback||noop,cbRef=callback,complete=testObject.complete||noop,needGroupSize,callbackKey;function handleGroup(needGroup,moreToCome){if(needGroup){if(isString(needGroup))moreToCome||(callback=function(){var args=[].slice.call(arguments);cbRef.apply(this,args),complete()}),loadScriptOrStyle(needGroup,callback,chain,0,testResult);else if(isObject(needGroup))for(callbackKey in needGroupSize=function(){var count=0,i;for(i in needGroup)needGroup.hasOwnProperty(i)&&count++;return count}(),needGroup)needGroup.hasOwnProperty(callbackKey)&&(moreToCome||--needGroupSize||(isFunction(callback)?callback=function(){var args=[].slice.call(arguments);cbRef.apply(this,args),complete()}:callback[callbackKey]=function(innerCb){return function(){var args=[].slice.call(arguments);innerCb&&innerCb.apply(this,args),complete()}}(cbRef[callbackKey])),loadScriptOrStyle(needGroup[callbackKey],callback,chain,callbackKey,testResult))}else!moreToCome&&complete()}dedup=!!testObject.dedup,handleGroup(group,!!always),always&&handleGroup(always)}if(isString(needs))loadScriptOrStyle(needs,0,chain,0);else if(isArray(needs))for(i=0;i<needs.length;i++)need=needs[i],isString(need)?loadScriptOrStyle(need,0,chain,0):isArray(need)?yepnope(need):isObject(need)&&loadFromTestObject(need,chain);else isObject(needs)&&loadFromTestObject(needs,chain)}).addPrefix=function(prefix,callback){prefixes[prefix]=callback},yepnope.addFilter=function(filter){globalFilters.push(filter)},yepnope.errorTimeout=1e4,null==doc.readyState&&doc.addEventListener&&(doc.readyState="loading",doc.addEventListener("DOMContentLoaded",handler=function(){doc.removeEventListener("DOMContentLoaded",handler,0),doc.readyState="complete"},0)),window.yepnope=getYepnope(),window.yepnope.executeStack=executeStack,window.yepnope.injectJs=injectJs,window.yepnope.injectCss=injectCss}(this,document);
  }

  function tree(_data) {
    let scanArr = [];
    let scanObj = [];
    let maxLevel = _data.length > 0 ? Object.keys(_data[0]).length : 0;
  
    const unique = function(arr, obj) {
      let _arr = [], _obj = [];
      for(let i=0; i<arr.length; i++) {
        if ( !_arr.includes( arr[i]) ) {
          _arr.push( arr[i] );
          _obj.push( obj[i] );
        }
      }
      return [_arr, _obj];
    };
  
    for (let i=0; i<maxLevel; i++) {
      let cid = `c${i+1}`;
      let arr = [], obj = [];
      for (let k in _data) {
        let item = _data[k];
        arr.push(item[cid]);
        obj.push({value:item[cid], label:item[cid], parent:item[`c${i}`], children:[]});
      }
      scanArr[i] = unique(arr, obj)[0];
      scanObj[i] = unique(arr, obj)[1];
    }
  
    let len = scanObj.length;
    for (let i=0; i<len; i++) {
      let curIdx = len-i-1;
      let nextIdx = len-i-2;
      let curItem = scanObj[curIdx];
      let nextItem = scanObj[nextIdx];
  
      for (let k in curItem) {
        let _current = curItem[k];
        for (let n in nextItem) {
          let _next = nextItem[n];
          if (_current.parent == _next.value) {
            _next.children.push(_current);
          }
        }
      }
    }
  
    for (let t=0; t<scanObj[0].length; t++) {
      delete scanObj[0][t].parent;
    }
    
    return scanObj[0];
  }

  function trimTreeData(arr) {
    for (let i=0; i<arr.length; i++) {
      let item = arr[i];
      if (item.children.length > 0) {
        if (item.children.length == 1 && !item.children[0].value) {
          delete item.children;
        } else {
          trimTreeData(item.children);
        }
      } else if (item.children && item.children.length == 0) {
        delete item.children;
      }
    }
  }

  function saveTextFile(str, fileName) {
    str = str || 'axlib';
    fileName = fileName || 'text.txt';
    const blob = new Blob([str], { type: 'application/json;charset=utf-8' });
    const objectURL = URL.createObjectURL(blob);

    const anchor = document.createElement('a');
    anchor.href = objectURL;
    anchor.download = fileName;
    anchor.click();

    URL.revokeObjectURL(objectURL);
  }

  function testExp(params) {
    params = params || {};
    let count = 0;
    let fn = function(){};
    let { exp='', success=fn, fail=fn, interval=200, timeout=5000, debug=false } = params;
    if (timeout === -1 || timeout === false) { timeout = 1e16; }
    let timer = setInterval(()=>{
        if (getNestedValue(window, exp)) {
            clearInterval(timer);
            success();
        } else {
            if (debug) { console.log(count); }
            count += interval;
            if (count >= timeout) {
                clearInterval(timer);
                fail();
            }
        }
    }, interval);
  }

  function getNestedValue(obj, key) {
    return key.split(".").reduce(function(result, key) {
       return result[key] 
    }, obj);
  }

  function getOverlapData(data1, data2) {
    // let arr = [JSON.stringify(data1), JSON.stringify(data2)];
    // arr.sort((a, b) => { return a.length < b.length; });
    // let str1 = arr[0], str2 = arr[1];
    // str1 = str1.substr(1,str1.length-2);
    // str2 = str2.substr(1,str2.length-2);
    // let str = str1.replace(str2, '');
    // str = str.replace(/\},,\{/g, '},{');
    // str = `[${str}]`;
    // return JSON.parse(str);

    let bigData = data1.length > data2.length ? data1 : data2;
    let smallData = data1.length < data2.length ? data1 : data2;
    smallData.forEach((a, i) => {
      let _a = JSON.stringify(a);
      bigData.forEach((b, j) => {
        let _b = JSON.stringify(b);
        if (_a == _b) { bigData[j] = null; }
      });
      bigData = bigData.filter(Boolean);
    });
    return bigData;
  }

  function getDiffData(data1, data2) {
    // 弃用，会误删相同的数据
    let diff = data1.concat(data2).map(item => JSON.stringify(item)).filter((value, idx, arr)=>{
      return arr.indexOf(value) == arr.lastIndexOf(value);
    });
    return JSON.parse(`[${diff}]`);
  }

  function loadJsModule(url, callback) {
    let script = document.createElement('script');
    script.onload = callback || function(){};
    script.setAttribute('type', 'module');
    script.setAttribute('src', url);
    document.querySelector('body').appendChild(script);
  }

}());